//@author A0086393U
package rightnow.ui;

import java.io.IOException;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rightnow.logic.Logic;
import rightnow.logic.LogicImpt;
import rightnow.logic.FeedbackProvider;
import rightnow.ui.widgets.ErrorDialogController;
import rightnow.ui.widgets.FeedbackController;
import rightnow.ui.widgets.ListController;
import rightnow.util.UiOptions;

/**
 * FX-Controller for the MainView.fxml display element. Contains the
 * UI logic that ties the entire UI together. MainController is an Observer, so
 * it can update itself automatically when notified without needing to be
 * passed around the entire program.
 * 
 * MainController is mainly in charge of the command line, so it passes 
 * commands to logic and for generation of Quick Help. In addition,
 * MainController also handles some keyboard shortcuts and generates
 * error dialogs when they occur.
 */
public class MainController extends AnchorPane implements Observer {
	private static final Logger LOG = LoggerFactory.getLogger(
			MainController.class);
	private static final String MAIN_SCENE_CSS = "/styles/main-scene.css";
	private static final String ERROR_FXML = "/fxml/ErrorDialog.fxml";
	private static final String ERROR_TITLE = "Error!";
	
	private static final String NEXT_CMD_STRING = "next";
	private static final String PREV_CMD_STRING = "prev";
	
	/* These are components in this section of the UI, and they will be
	 * injected into by FXML automatically.
	 * 
	 * DO NOT CREATE NEW INSTANCES OF THESE! (unless you know what you are doing)
	 */
	@FXML private TextField cmdLine;
	@FXML private FeedbackController feedbackField;
	@FXML private ListController listSpace;
	
	private Logic mainLogic;
	private CommandStack cmdStack;
	
	// Called after the FXML file specifying this class as fx:controller is loaded
	@FXML
	public void initialize() throws IOException {
		LOG.info("Creating main controller");
		
		assert cmdLine != null : "fx:id=\"cmdLine\" was not injected";
		assert feedbackField != null : "fx:id=\"feedbackField\" was not injected";
		assert listSpace != null : "fx:id=\"listSpace\" was not injected";
		LOG.debug("Fxml dependencies successfully injected");
		
		FeedbackProvider.getInstance().addObserver(this);
		
		clearCmdLine();

		mainLogic = new LogicImpt();
		try {
			mainLogic.initStorage();
		} catch (Exception e) {
			generateErrorDialog(e);
			System.exit(1);
		}
		LOG.debug("Logic and storage successfully created");
		
		cmdStack = new CommandStack();
		LOG.debug("Command stack successfully created");

		LOG.info("Main controller successfully created");
	}
	
	@FXML
	protected void handleOnAction() {
		String input = cmdLine.getText();
		LOG.info("Input " + input + " being executed");
		cmdStack.commandEntered(input);
		clearCmdLine();
		
		// Send the input to the main parser
		try {
			// index only, toggle task details if it is an integer on the page
			if (isIndexOnly(input) && 
					listSpace.toggleTaskDetails(Integer.parseInt(input))) {
			} else {
				mainLogic.executeCommand(input);
			}
		} catch (Exception e) {
			generateErrorDialog(e);
			System.exit(1);
		}
		
		LOG.info("Input " + input + " successfully executed");
	}
	
	@FXML
	protected void handleOnKeyReleased(KeyEvent ke) throws IOException {
		String input = cmdLine.getText();

		if (ke.getCode() == KeyCode.UP) {
			String history = cmdStack.getBackwards();
			if (history != null) {
				replaceCmdLine(history);
				LOG.debug("Display previous command in history");
				return;
			}
		}
		
		if (ke.getCode() == KeyCode.DOWN) {
			String history = cmdStack.getForward();
			if (history != null) {
				replaceCmdLine(history);
				LOG.debug("Display next command in history");
				return;
			}
		}
		
		if (ke.getCode() == KeyCode.TAB) {
			selectNextWordCmdLine();
			LOG.debug("Select the next word");
			return;
		}
		
		if (ke.getCode() == KeyCode.LEFT && ke.isControlDown()) {
			prevPage();
			LOG.debug("Hotkey used to go to previous page");
			return;
		}

		if (ke.getCode() == KeyCode.RIGHT && ke.isControlDown()) {
			nextPage();
			LOG.debug("Hotkey used to go to next page");
			return;
		}
		
		// Generate quick help msg if key released is not a special shortcut
		if (ke.getCode() != KeyCode.ENTER) {
				feedbackField.generateQuickHelpMsg(input);
		}
	}
	
	/**
	 * Checks if the input string is a single-digit integer not larger than the
	 * larger index on the screen.
	 * @param input Input string.
	 * @return True if the input string is a possible task display index.
	 */
	private boolean isIndexOnly(String input) {
		// Quickly reject if the length of the input is longer than the number of
		// digits in the maximum number of tasks per page.
		if (input.length() <= String.valueOf(UiOptions.TASKS_PER_PAGE).length()) {
			try {
				int number = Integer.parseInt(input);
				if (number > 0 && number <= UiOptions.TASKS_PER_PAGE) {
					return true;
				} 
			} catch (NumberFormatException nfe) {
				// not a valid integer, just return false
				return false;
			}
		}
		
		return false;
	}

	private void clearCmdLine() {
		cmdLine.clear();
	}
	
	public void replaceCmdLine(String cmd) {
		cmdLine.setText(cmd);
		cmdLine.end();
	}
	
	private void selectNextWordCmdLine() {
		if (cmdLine.getCaretPosition() >= cmdLine.getLength()) {
			cmdLine.home();
			cmdLine.selectEndOfNextWord();
		} else {
			cmdLine.nextWord();
			cmdLine.selectEndOfNextWord();
		}
	}
	
	private void nextPage() throws IOException {
		// Simulate passing next command to parser
		mainLogic.executeCommand(NEXT_CMD_STRING);
	}
	
	private void prevPage() throws IOException {
		// Simulate passing prev command to parser
		mainLogic.executeCommand(PREV_CMD_STRING);
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o == FeedbackProvider.getInstance()) {
			FeedbackProvider fp = FeedbackProvider.getInstance();
			if (fp.hasCmdLineChanged()) {
				cmdLine.setText(fp.getCmdLine());
				cmdLine.end();
			}
		}
	}
	
	/**
	 * Generates a new error dialog window displaying an exception message.
	 * @param e Exception that caused the error dialog.
	 */
	private void generateErrorDialog(Exception e) {
		Stage stage = new Stage();
		URL errorFxmlUrl = getClass().getResource(ERROR_FXML);
		FXMLLoader loader = new FXMLLoader();
		loader.setLocation(errorFxmlUrl);
		try {
			Parent p = (Parent) loader.load(errorFxmlUrl.openStream());
			ErrorDialogController errorController = loader.getController();
			errorController.setErrorMsg(e.getMessage());
			e.printStackTrace();
			
			Scene errorScene = new Scene(p);
			errorScene.getStylesheets().add(MAIN_SCENE_CSS);
			stage.setScene(errorScene);
		} catch (IOException ioe) {
			LOG.error("Error opening error dialog fxml");
			System.exit(1);
		}
	
			stage.setTitle(ERROR_TITLE);
			stage.setResizable(false);
			stage.showAndWait();
			cmdLine.getScene().getWindow().hide();
	}
}
